﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.draw;

namespace TravellerSectorBooklet {
  /// <summary>
  /// Static class for extension methods
  /// </summary>
  public static class Extensions {
    /// <summary>
    /// Draws a regular polygon.
    /// </summary>
    /// <param name="canvas">The canvas.</param>
    /// <param name="centerX">The center X.</param>
    /// <param name="centerY">The center Y.</param>
    /// <param name="radius">The radius.</param>
    /// <param name="numberOfSides">The number of sides.</param>
    /// <param name="rotation">The rotation.</param>
    public static void RegularPolygon(this PdfContentByte canvas, float centerX, float centerY, float radius, int numberOfSides, double rotation = 0) {
      double angle = Math.PI * 2 / numberOfSides; // rotation between two vertices
      canvas.MoveTo(centerX + (float)(radius * Math.Cos(rotation)), centerY + (float)(radius * Math.Sin(rotation)));
      for (int i = 1; i < numberOfSides; i++)
        canvas.LineTo(centerX + (float)(radius * Math.Cos(i * angle + rotation)), centerY + (float)(radius * Math.Sin(i * angle + rotation)));
      canvas.ClosePath();
    }
    /// <summary>
    /// Draws a regular polygon.
    /// </summary>
    /// <param name="canvas">The canvas.</param>
    /// <param name="rectangle">The clipping rectangle.</param>
    /// <param name="centerX">The center X.</param>
    /// <param name="centerY">The center Y.</param>
    /// <param name="radius">The radius.</param>
    /// <param name="numberOfSides">The number of sides.</param>
    /// <param name="rotation">The rotation.</param>
    /// <remarks>Since this uses <see cref="ClipLineTo"/>, we can't use <code>ClosePath</code>.  Instead we draw an extra line segment.</remarks>
    public static void ClippedRegularPolygon(this PdfContentByte canvas, Rectangle rectangle, float centerX, float centerY, float radius, int numberOfSides, double rotation = 0) {
      double angle = Math.PI * 2 / numberOfSides; // rotation between two vertices
      canvas.MoveTo(centerX + (float)(radius * Math.Cos(rotation)), centerY + (float)(radius * Math.Sin(rotation)));
      for (int i = 1; i <= numberOfSides; i++)
        canvas.ClipLineTo(rectangle, centerX + (float)(radius * Math.Cos(i * angle + rotation)), centerY + (float)(radius * Math.Sin(i * angle + rotation)));
    }
    /// <summary>
    /// Draws a regular star.
    /// </summary>
    /// <param name="canvas">The canvas.</param>
    /// <param name="centerX">The center X.</param>
    /// <param name="centerY">The center Y.</param>
    /// <param name="radius">The radius.</param>
    /// <param name="numberOfPoints">The number of points.</param>
    /// <param name="rotation">The rotation.</param>
    /// <param name="insetScale">The inset scale.</param>
    /// <remarks>The <code>insetScale</code> controls how much the concave vertices are inset compared to the convex ones.  The default value of 1
    /// will places them half as far from the center point as the convex ones.  A value of 2 would place them a third the distance, etc.</remarks>
    public static void Star(this PdfContentByte canvas, float centerX, float centerY, float radius, int numberOfPoints, double rotation = 0, float insetScale = 1f) {
      double angle = Math.PI / numberOfPoints; // rotation between two vertices
      canvas.MoveTo(centerX + (float)(radius * Math.Cos(rotation)), centerY + (float)(radius * Math.Sin(rotation)));
      for (int i = 1; i < 2 * numberOfPoints; i++)
        canvas.LineTo(centerX + (float)(radius / (1 + insetScale * (i & 1)) * Math.Cos(rotation)), centerY + (float)(radius / (1 + insetScale * (i & 1)) * Math.Sin(rotation)));
      canvas.ClosePath();
    }

    /// <summary>
    /// Draws a line line to the given point clipped to the rectangle.
    /// </summary>
    /// <param name="canvas">The canvas.</param>
    /// <param name="clippingRectangle">The clipping rectangle.</param>
    /// <param name="xCoordinate">The x coordinate.</param>
    /// <param name="yCoordinate">The y coordinate.</param>
    public static void ClipLineTo(this PdfContentByte canvas, Rectangle clippingRectangle, float xCoordinate, float yCoordinate) {
      // get the current draw position, which will be the last occurrence of xx yy [lm] in the buffer
      string s;
      try {
        s = canvas.ToString();
      } catch {
        s = Encoding.ASCII.GetString(canvas.InternalBuffer.Buffer);
      }
      Match match = Regex.Match(s, @".*(-?\d+\.?\d*) (-?\d+\.?\d*) [lm].*?", RegexOptions.Multiline | RegexOptions.RightToLeft);
      float mx = Convert.ToSingle(match.Groups[1].Value), my = Convert.ToSingle(match.Groups[2].Value);

      // if both start and end are within the clippingRectangle, nothing special needs doing

      if (mx >= clippingRectangle.Left && mx <= clippingRectangle.Right && xCoordinate >= clippingRectangle.Left && xCoordinate <= clippingRectangle.Right && my >= clippingRectangle.Bottom && my <= clippingRectangle.Top && yCoordinate >= clippingRectangle.Bottom && yCoordinate <= clippingRectangle.Top) {
        canvas.LineTo(xCoordinate, yCoordinate);
        return;
      }

      // boundary conditions
      if (((xCoordinate == clippingRectangle.Left || mx == clippingRectangle.Left) && (xCoordinate < clippingRectangle.Left || mx < clippingRectangle.Left)) ||
          ((xCoordinate == clippingRectangle.Right || mx == clippingRectangle.Right) && (xCoordinate > clippingRectangle.Right || mx > clippingRectangle.Right)) ||
          ((yCoordinate == clippingRectangle.Bottom || my == clippingRectangle.Bottom) && (yCoordinate < clippingRectangle.Bottom || my < clippingRectangle.Bottom)) ||
          ((yCoordinate == clippingRectangle.Top || my == clippingRectangle.Top) && (yCoordinate > clippingRectangle.Top || my > clippingRectangle.Top))) {
        canvas.MoveTo(xCoordinate, yCoordinate);
        return;
      }
      // at least one of the points is outside the clippingRectangle

      float sx, sy, ex, ey, dx, dy;
      bool addMove = true;
      float m;

      dx = xCoordinate - mx; dy = yCoordinate - my;

      if (dx == 0) { // special case, vertical line
        if (xCoordinate < clippingRectangle.Left || xCoordinate > clippingRectangle.Right) { // line is entirely outside box
          canvas.MoveTo(xCoordinate, yCoordinate);
          return;
        }
        if (my < clippingRectangle.Bottom)
          sy = clippingRectangle.Bottom;
        else if (my > clippingRectangle.Top)
          sy = clippingRectangle.Top;
        else {
          sy = my;
          addMove = false;
        }
        if (yCoordinate < clippingRectangle.Bottom)
          ey = clippingRectangle.Bottom;
        else if (yCoordinate > clippingRectangle.Top)
          ey = clippingRectangle.Top;
        else
          ey = yCoordinate;
        if (addMove)
          canvas.MoveTo(xCoordinate, sy);
        canvas.LineTo(xCoordinate, ey);
        return;
      }
      if (dy == 0) { // special case, horizontal line
        if (yCoordinate < clippingRectangle.Bottom || yCoordinate > clippingRectangle.Top) { // line is entirely outside box
          canvas.MoveTo(xCoordinate, yCoordinate);
          return;
        }
        if (mx < clippingRectangle.Left)
          sx = clippingRectangle.Left;
        else if (mx > clippingRectangle.Right)
          sx = clippingRectangle.Right;
        else {
          sx = mx;
          addMove = false;
        }
        if (xCoordinate < clippingRectangle.Left)
          ex = clippingRectangle.Left;
        else if (xCoordinate > clippingRectangle.Right)
          ex = clippingRectangle.Right;
        else
          ex = xCoordinate;
        if (addMove)
          canvas.MoveTo(sx, yCoordinate);
        canvas.LineTo(ex, yCoordinate);
        return;
      }

      // first adjust the start

      if (mx >= clippingRectangle.Left && mx <= clippingRectangle.Right && my >= clippingRectangle.Bottom && my <= clippingRectangle.Top) { // start is valid
        addMove = false;
        sx = mx;
        sy = my;
      } else {
        m = dy / dx; // slope of the line
        sx = mx; sy = my;
        if (mx < clippingRectangle.Left) {
          sy = my + m * (clippingRectangle.Left - mx);
          sx = clippingRectangle.Left;
        } else if (mx > clippingRectangle.Right) {
          sy = my + m * (clippingRectangle.Right - mx);
          sx = clippingRectangle.Right;
        }
        if (sy < clippingRectangle.Bottom) {
          sx = sx + (clippingRectangle.Bottom - sy) / m;
          sy = clippingRectangle.Bottom;
        } else if (sy > clippingRectangle.Top) {
          sx = sx + (clippingRectangle.Top - sy) / m;
          sy = clippingRectangle.Top;
        }
      }

      // and then adjust the end

      if (xCoordinate >= clippingRectangle.Left && xCoordinate <= clippingRectangle.Right && yCoordinate >= clippingRectangle.Bottom && yCoordinate <= clippingRectangle.Top) { // start is valid
        ex = xCoordinate;
        ey = yCoordinate;
      } else {
        m = dy / dx; // slope of the line
        ex = xCoordinate; ey = yCoordinate;
        if (xCoordinate < clippingRectangle.Left) {
          ey = yCoordinate + m * (clippingRectangle.Left - xCoordinate);
          ex = clippingRectangle.Left;
        } else if (xCoordinate > clippingRectangle.Right) {
          ey = yCoordinate + m * (clippingRectangle.Right - xCoordinate);
          ex = clippingRectangle.Right;
        }
        if (ey < clippingRectangle.Bottom) {
          ex = ex + (clippingRectangle.Bottom - ey) / m;
          ey = clippingRectangle.Bottom;
        } else if (ey > clippingRectangle.Top) {
          ex = ex + (clippingRectangle.Top - ey) / m;
          ey = clippingRectangle.Top;
        }
      }

      // draw the adjusted line

      if (addMove)
        canvas.MoveTo(sx, sy);
      canvas.LineTo(ex, ey);

      // if the end was outside the clip, need to move the position to where it would have been without clipping

      if (ex != xCoordinate || ey != yCoordinate)
        canvas.MoveTo(xCoordinate, yCoordinate);
    }
  }
}
